package com.davydov.suabl.model;

import java.util.ArrayList;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import com.davydov.suabl.Position;
import com.davydov.suabl.Force;

public class PhysicsEngine extends Thread implements KeyListener {
	public boolean pleaseWait = false;
	
	private Environment env;
	private int PixelsPerMeter;		// Conversion factor for pixels to meters
	private double G;				// Universal Gravitational Constant
	private double SecPerTick;		// Conversion factor for seconds per tick
	
	public PhysicsEngine(int env_x, int env_y, EngineProperties settings) {
		env = new Environment(env_x, env_y);
		this.PixelsPerMeter = settings.getPixelsPerMeter();
		this.G = settings.getG();
		this.SecPerTick = settings.getSecPerTick();
	}
	
	/*
	 * pre-cond: none
	 * post-cond: Objects in the arraylist objs are added to the environment inside of PhysicsEngine
	 * 		one at a time. If an object with the same position already exists in the environment, the
	 * 		new object will replace the old. If an object outside the bounds of the environment exists
	 * 		in the arraylist objs, all objects before it are added to the environment and the
	 * 		OutsideEnvironmentException is thrown.
	 */
	public void addObjects(ArrayList<PhysicsObject> objs) throws OutsideEnvironmentException {
		for(int i = 0; i < objs.size(); i++) {
			env.add(objs.get(i));
		}
	}
	
	/*
	 * pre-cond: none
	 * post-cond: method returns all objects present in the environment inside an ArrayList.
	 */
	public ArrayList<PhysicsObject> getObjects() {
		return env.getAllObjects();
	}

	@Override
	public void run() {
		while(true) {
			synchronized(this) {
				// Check if the thread should wait
				while(pleaseWait) {
					try {
						this.wait();
					} catch(Exception e) {}
				}
			}
			
			// Continue execution
			ArrayList<PhysicsObject> objects = env.getAllObjects();
			for(int i = 0; i < objects.size(); i++) {
				PhysicsObject obj = objects.get(i);
				obj.tick(env);
				obj.setPosition(this.newPos(obj));
			}
		}
	}
	
	/*
	 * pre-cond: PhysicsObject obj is in the environment
	 * post-cond: method returns the new position to be assigned to PhysicsObject obj based on
	 * 		simplified physics and the current state of the environment.
	 */
	private Position newPos(PhysicsObject obj) {
		//these lines go somewhere, but I don't know where :(
		//calculate x and y components of force
		double x = obj.getNetForce().getMagnitude() * Math.cos(obj.getNetForce().getDirection());
		double y = obj.getNetForce().getMagnitude() * Math.sin(obj.getNetForce().getDirection());
		double realDirection = 0;
		//calculate net gravitational field
			for(int z = 0; z < env.getAllObjects().size(); z++) {
				if (env.getAllObjects().get(z).equals(obj)){}
				else
				{
					double tentativeDirection;
					//runs through environment adding x and y components to get resultant y and x components of the net Force
					PhysicsObject affector = env.getAllObjects().get(z);
					double distance = PixelsPerMeter * Math.sqrt((Math.pow((affector.getPosition().getX() - obj.getPosition().getX()),2)) + 
							Math.pow(affector.getPosition().getY() - obj.getPosition().getY(), 2));
					if ((obj.getPosition().getX() == affector.getPosition().getX()) && (affector.getPosition().getY() < obj.getPosition().getY()))
					{
						tentativeDirection = Math.PI/2;
					}
					else if ((obj.getPosition().getX() == affector.getPosition().getX()) && (affector.getPosition().getY() > obj.getPosition().getY()))//Stas' suggestion to have more parenthesis than necessary
					{
						tentativeDirection = -Math.PI/2;
					}
					else if ((obj.getPosition().getX() == affector.getPosition().getX()) && (affector.getPosition().getY() == obj.getPosition().getY()))
					{}
					
					else
					{
						//tangent of affecting angle is the arc tan of difference in the y's / distance between x's
						tentativeDirection = Math.abs(Math.atan(Math.abs(affector.getPosition().getY() - obj.getPosition().getY())/Math.abs(affector.getPosition().getX() - obj.getPosition().getX())));
						//if the affector's x is greater than obj's x and the same is true for y's, then it's in the first quadrant
						if ( affector.getPosition().getX() - obj.getPosition().getX() > 0 && affector.getPosition().getY() - obj.getPosition().getY() < 0)
							{realDirection = tentativeDirection;}
						//if the affector's x is greater than the obj's x, but the affector's y is less than the obj's y it's in the 4th quadrant
						else if( affector.getPosition().getX() - obj.getPosition().getX() > 0 && affector.getPosition().getY() - obj.getPosition().getY() < 0)
							{realDirection = (2 * Math.PI) - tentativeDirection;}
						//if the affector's x is less than the object's, and the same is true about the y, then it's in the 3rd quadrant
						else if (affector.getPosition().getX() - obj.getPosition().getX() < 0 && affector.getPosition().getY() - obj.getPosition().getY() > 0)
							{realDirection = Math.PI + tentativeDirection;}
						//otherwise it'd be in the 2nd quadrant, but just for good measure, I'll define the condition since I probably messed up somewhere
						else if (affector.getPosition().getX() - obj.getPosition().getX() < 0 && affector.getPosition().getY() - obj.getPosition().getY() < 0)
							{realDirection = Math.PI - tentativeDirection;}
					}
					Force resultantForce = new Force(realDirection, G * obj.getMass() * affector.getMass() / (Math.pow(distance,2)));
					x += resultantForce.getMagnitude() * Math.cos(resultantForce.getDirection());
					y += resultantForce.getMagnitude() * Math.sin(resultantForce.getDirection());
					
					
				}		
			}
			//x and y now have factored in gravity
			//tangent of affecting angle is the arc tan of difference in the y's / distance between x's
			double tentativeDirection = Math.abs(Math.atan(Math.abs(y)/Math.abs(x)));
			//if the affector's x is greater than obj's x and the same is true for y's, then it's in the first quadrant
			if ( x > 0 && y < 0)
				{realDirection = tentativeDirection;}
			//if the affector's x is greater than the obj's x, but the affector's y is less than the obj's y it's in the 4th quadrant
			else if( x > 0 && y < 0)
				{realDirection = (2 * Math.PI) - tentativeDirection;}
			//if the affector's x is less than the object's, and the same is true about the y, then it's in the 3rd quadrant
			else if (x < 0 && y > 0)
				{realDirection = Math.PI + tentativeDirection;}
			//otherwise it'd be in the 2nd quadrant, but just for good measure, I'll define the condition since I probably messed up somewhere
			else if (x < 0 && y < 0)
				{realDirection = Math.PI - tentativeDirection;}
			//this is the final force
			Force iHateThis = new Force(realDirection,Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))); //Are you freakin' kiddin' me, this better work.
			
		//components of the final position
		double finalX = 0;
		double finalY = 0;
		
		// calculate new position via x = initial x + initial velocity * time + 1/2 acceleration * time 
		// acceleration is the component of the force (x or y) over the mass of the object via F = ma
		finalX = obj.getPosition().getX() + obj.getVelocity().getSpeed() * SecPerTick + .5 * (iHateThis.getMagnitude() * Math.cos(iHateThis.getDirection()))/obj.getMass() * SecPerTick;
		finalY = obj.getPosition().getY() + obj.getVelocity().getSpeed() * SecPerTick + .5 * (iHateThis.getMagnitude() * Math.sin(iHateThis.getDirection()))/obj.getMass() * SecPerTick;
		
		return new Position((int)finalX,(int)finalY);
		
		// calculate net electrical field
		
	}

	@Override
	public void keyPressed(KeyEvent e) {}

	@Override
	public void keyReleased(KeyEvent e) {}

	@Override
	public void keyTyped(KeyEvent e) {
		env.addKey(e);
	}
}

